package com.yexx.starter.optms.idempotent.interceptor;

import com.yexx.starter.optms.idempotent.IdempotentProperties;
import com.yexx.starter.optms.idempotent.annotation.Idempotent;
import com.yexx.starter.optms.idempotent.exception.IdempotentException;
import com.yexx.starter.optms.idempotent.keygen.LockKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ExecutionException;

/**
 * description: 幂等拦截器
 *
 * @author: zuomin (myleszelic@outlook.com)
 * @date 2022/06/01:22:33
 */
@Slf4j
public class IdempotentInterceptor implements HandlerInterceptor, Ordered, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private final IdempotentProperties idempotentProperties;
    private final RedissonClient redissonClient;

    public IdempotentInterceptor(IdempotentProperties idempotentProperties, RedissonClient redissonClient) {
        this.idempotentProperties = idempotentProperties;
        this.redissonClient = redissonClient;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Idempotent annotationIdempotent = handlerMethod.getMethod().getAnnotation(Idempotent.class);
            if (annotationIdempotent == null) {
                return true;
            }
            LockKeyGenerator keyGenerator = null;
            try {
                keyGenerator = applicationContext.getBean(annotationIdempotent.keyGenerator());
            } catch (BeansException e) {
                // ignore
            }
            if (keyGenerator == null) {
                log.error("not found keyGenerator={} bean in spring project,Idempotent not ok", annotationIdempotent.keyGenerator().getSimpleName());
                return true;
            }
            String key = String.format("%s:%s", annotationIdempotent.lockKeyPrefix(), keyGenerator.resolver(annotationIdempotent, request, response, handlerMethod));
            RLock lock = redissonClient.getLock(key);
            if (lock.isLocked()) {
                throw new IdempotentException(annotationIdempotent.errCode(), annotationIdempotent.errMsg());
            }
            boolean tryLock = lock.tryLock(annotationIdempotent.waitTime(), annotationIdempotent.expiredTime(), annotationIdempotent.timeUnit());
            if (!tryLock) {
                throw new IdempotentException(annotationIdempotent.errCode(), annotationIdempotent.errMsg());
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod  = (HandlerMethod) handler;
            Idempotent annotationIdempotent = handlerMethod.getMethodAnnotation(Idempotent.class);
            if (annotationIdempotent == null) {
                return;
            }
            LockKeyGenerator keyGenerator = null;
            try {
                keyGenerator = applicationContext.getBean(annotationIdempotent.keyGenerator());
            } catch (BeansException e) {
                //ignore
            }
            if (keyGenerator == null) {
                log.error("not found keyGenerator={} bean in spring project,Idempotent not ok", annotationIdempotent.keyGenerator().getSimpleName());
                return;
            }
            String key = String.format("%s:%s", annotationIdempotent.lockKeyPrefix(), keyGenerator.resolver(annotationIdempotent, request, response, handlerMethod));
            RLock lock = redissonClient.getLock(key);
            //如果配置 不删除key 直到过期才自动取消
            if (lock.isHeldByCurrentThread() && annotationIdempotent.unlockKey()) {
                try {
                    lock.forceUnlockAsync().get();
                } catch (ExecutionException | InterruptedException e) {
                    log.error("解锁失败", e);
                }
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    @Override
    public int getOrder() {
        Integer orderValue = idempotentProperties.getIdempotentInterceptorOrder();
        if (orderValue == null) {
            orderValue = Ordered.LOWEST_PRECEDENCE;
        }
        return orderValue;
    }
}
